//=========================================================================
// Copyright (C) 2024 The C++ Component Model(COMO) Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//=========================================================================

/*  =========================================================================
    zmtp_tcp_endpoint - TCP endpoint class

    Copyright (c) contributors as noted in the AUTHORS file.
    This file is part of libzmtp, the C ZMTP stack.

    This Source Code Form is subject to the terms of the Mozilla Public
    License, v. 2.0. If a copy of the MPL was not distributed with this
    file, You can obtain one at http://mozilla.org/MPL/2.0/.
    =========================================================================
*/

#include "zmtp_classes.h"

CMemPool *g_zmtp_tcp_endpoint_buffer = NULL;

/**
 * zmtp_tcp_endpoint_new
 */
zmtp_tcp_endpoint_t *zmtp_tcp_endpoint_new(const char *ip_addr, unsigned short port)
{
    zmtp_tcp_endpoint_t *self = NULL;
    if (NULL != g_zmtp_tcp_endpoint_buffer) {
        self = (zmtp_tcp_endpoint_t *)CMemPool_Alloc(g_zmtp_tcp_endpoint_buffer,
                                 sizeof(zmtp_tcp_endpoint_t), MUST_USE_MEM_POOL);
    }
    if (NULL == self) {
        self = (zmtp_tcp_endpoint_t *)zmalloc(sizeof(zmtp_tcp_endpoint_t));
    }

    if (NULL == self) {
        return NULL;
    }

    // Initialize base class
    self->base = (zmtp_endpoint_t) {
        .connect = (int (*) (zmtp_endpoint_t *)) zmtp_tcp_endpoint_connect,
        .listen  = (int (*) (zmtp_endpoint_t *)) zmtp_tcp_endpoint_listen,
        .destroy = (void (*) (zmtp_endpoint_t **)) zmtp_tcp_endpoint_destroy,
    };

    // Resolve address
    const struct addrinfo hints = {
        .ai_family   = AF_INET,
        .ai_socktype = SOCK_STREAM,
        .ai_flags    = AI_NUMERICHOST | AI_NUMERICSERV
    };
    char service[8 + 1];

    snprintf(service, sizeof(service), "%u", port);
    if (getaddrinfo(ip_addr, service, &hints, &self->addrinfo)) {
        if ((NULL != g_zmtp_tcp_endpoint_buffer) &&
                      (CMemPool_CheckExist(g_zmtp_tcp_endpoint_buffer, self))) {
            CMemPool_Free(g_zmtp_tcp_endpoint_buffer, self);
        }
        else {
            zfree(self);
        }

        return NULL;
    }

    return self;
}

/**
 * zmtp_tcp_endpoint_destroy
 */
void zmtp_tcp_endpoint_destroy(zmtp_tcp_endpoint_t **self_p)
{
    if (NULL == self_p) {
        return;
    }

    if (NULL != *self_p) {
        zmtp_tcp_endpoint_t *self = *self_p;
        freeaddrinfo(self->addrinfo);
        if ((NULL != g_zmtp_tcp_endpoint_buffer) &&
                      (CMemPool_CheckExist(g_zmtp_tcp_endpoint_buffer, self))) {
            CMemPool_Free(g_zmtp_tcp_endpoint_buffer, self);
        }
        else {
            zfree(self);
        }
        *self_p = NULL;
    }
}

/**
 * zmtp_tcp_endpoint_connect
 */
int zmtp_tcp_endpoint_connect(zmtp_tcp_endpoint_t *self)
{
    if (NULL == self) {
        return -1;
    }

    const int s = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == s) {
        return -2;
    }

    struct timeval timeout = {TCP_TIMEOUT, 0};
    int optval = 1;
    setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, (const char*)&timeout, sizeof(timeout));
    setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout));
    setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval));

    const int rc = connect(s, self->addrinfo->ai_addr, self->addrinfo->ai_addrlen);
    if (-1 == rc) {
        close(s);
        return -3;
    }

    return s;
}

/**
 * zmtp_tcp_endpoint_listen
 */
int zmtp_tcp_endpoint_listen(zmtp_tcp_endpoint_t *self)
{
    if (NULL == self) {
        return -1;
    }

    const int s = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == s) {
        return -2;
    }

    struct timeval timeout = {TCP_TIMEOUT, 0};
    const int optval = 1;
    setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout));
    setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
    setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval));

    int rc = bind(s, self->addrinfo->ai_addr, self->addrinfo->ai_addrlen);
    if (0 == rc) {
        /**
         * The backlog argument (the second one) defines the maximum length to
         * which the queue of pending connections for sockfd may grow.  If a
         * connection request arrives when the queue is full, the client may
         * receive an error with an indication of ECONNREFUSED or, if the
         * underlying protocol supports retransmission, the request may be
         * ignored so that a later reattempt at connection succeeds.
         */
        rc = listen(s, 1);
        if (0 == rc) {
            struct sockaddr_in addr;
            int addr_len = sizeof(addr);
            rc = accept(s, (struct sockaddr *)&addr, (socklen_t*)&addr_len);
        }
    }
    close(s);

    return rc;
}
